package com.ruoyi.system.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.domain.entity.SysAttachment;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.Threads;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.system.dao.SysAttachmentDao;
import com.ruoyi.system.service.ISysAttachmentService;

import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.SetBucketPolicyArgs;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 附件Service业务层处理
 * 
 * @author ruoyi
 * @date 2025-08-02
 */
@Service
@Slf4j
public class SysAttachmentServiceImpl implements ISysAttachmentService {
	@Autowired
	private SysAttachmentDao sysAttachmentDao;
	@Autowired
	private MinioClient client;
	private Map<String, Object> buckets = new HashMap<String, Object>();
	@Value("${storage.minio.endpoint:}")
	private String minioBaseUrl;

	/**
	 * 查询附件
	 * 
	 * @param id 附件主键
	 * @return 附件
	 */
	@Override
	public SysAttachment selectById(String id) {
		return sysAttachmentDao.selectById(id);
	}

	/**
	 * 查询附件列表
	 * 
	 * @return 附件
	 * @throws Exception
	 */
	@Override
	public List<Map<String, Object>> queryList(Map<String, Object> param) throws Exception {
		List<Map<String, Object>> list = sysAttachmentDao.queryList(param);
		// BeanUtils.caseFormat(list); 在Mybatis拦截器插件中统一处理
		return list;
	}

	/**
	 * 查询附件列表
	 * 
	 * @param sysAttachment 附件
	 * @return 附件
	 */
	@Override
	public List<SysAttachment> selectList(SysAttachment sysAttachment) {
		return sysAttachmentDao.selectList(sysAttachment);
	}

	/**
	 * 新增附件
	 * 
	 * @param sysAttachment 附件
	 * @return 结果
	 */
	@Override
	public int insert(SysAttachment sysAttachment) {
		return sysAttachmentDao.insert(sysAttachment);
	}

	/**
	 * 修改附件
	 * 
	 * @param sysAttachment 附件
	 * @return 结果
	 */
	@Override
	public int update(SysAttachment sysAttachment) {
		//sysAttachment.setUpdateTime(DateUtils.getNowDate());
		return sysAttachmentDao.update(sysAttachment);
	}

	/**
	 * 批量删除附件
	 * 
	 * @param ids 需要删除的附件主键
	 * @return 结果
	 */
	@Override
	public int deleteByIds(String[] ids) {
		return sysAttachmentDao.deleteByIds(ids);
	}

	/**
	 * 删除附件信息
	 * 
	 * @param id 附件主键
	 * @return 结果
	 */
	@Override
	public int deleteById(String id) {
		return sysAttachmentDao.deleteById(id);
	}

	@Override
	public JSONObject upload(MultipartFile file, String bucketName) throws Exception {
		JSONObject result = new JSONObject();
		InputStream in = null;
		ByteArrayOutputStream byteArrayOut = null;
		try {
			// 判断文件是否为空
			if (null == file || 0 == file.getSize()) {
				throw new Exception("文件对象不能为空！");
			}
			// 文件名
			String originalFilename = file.getOriginalFilename();
			if (originalFilename.contains("..")) {
				throw new RuntimeException("文件名含有非法字符串");
			}
			// 判断存储桶是否存在 不存在则创建
			if (StringUtils.isEmpty(bucketName)) {
				throw new RuntimeException("bucketName is null");
			}
			// 多节点存在上传服务时,确保只有一个节点执行,并且存在重试机制
			createBucket(bucketName);// 每天创建一个bucket,存在创建超时异常

			// 新的文件名 = 存储桶文件名_时间戳.后缀名
			assert originalFilename != null;
			String datetime = DateFormatUtils.format(new Date(), "yyyy/MM/dd");
			String objectName = datetime + "/" + UUID.randomUUID().toString()
					+ originalFilename.substring(originalFilename.lastIndexOf("."));

			// 开始上传
			in = file.getInputStream();
			String contentType = file.getContentType();
			long size = file.getSize();
			byteArrayOut = putObject(bucketName, result, in, objectName, contentType, size);
		} catch (Exception e) {
			e.printStackTrace();
			result.put("description", e.getMessage());
			result.put("error", "-1");
		} finally {
			IOUtils.close(in);
			IOUtils.close(byteArrayOut);
		}
		return result;
	}
	
	public void createBucket(String bucketName) {
		if(!buckets.containsKey(bucketName)) {
			int times = 0;
			boolean isCreateBucket = false;
			while(times < 10) {
				try {
					createPublicBucket(bucketName);
					isCreateBucket = true;
					break;
				} catch (Exception e) {
					e.printStackTrace();
					times++;
					Threads.sleep(5000L);
				}
			}
			if(!isCreateBucket) {
				throw new RuntimeException("创建bucket失败");
			}
			buckets.put(bucketName, isCreateBucket);
		}
		
	}
	
	private ByteArrayOutputStream putObject(String bucketName, JSONObject result, InputStream in, String objectName,
			String contentType, long size) throws IOException {
		ByteArrayOutputStream byteArrayOut = null;
		byteArrayOut = new ByteArrayOutputStream();
		int len;
		byte[] bytes = new byte[1024];
		while ((len = in.read(bytes)) != -1) {
		    byteArrayOut.write(bytes, 0, len);
		}
		byte[] byteArray = byteArrayOut.toByteArray();
		putObject(bucketName, result, objectName, contentType, size, byteArray);
		return byteArrayOut;
	}
	
	private void putObject(String bucketName, JSONObject result, String objectName, String contentType, long size,
			byte[] byteArray) throws IOException {
		boolean isPutObject = false;
		int attempts = 0;
		while(attempts < 10) {
			ByteArrayInputStream byteArrayIn = new ByteArrayInputStream(byteArray);
			try {
				client.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName)
						.stream(byteArrayIn, size, -1)
						.contentType(contentType).build());
				isPutObject = true;
				break;
			} catch (Exception e) {
				e.printStackTrace();
				attempts++;
				Threads.sleep(5000L);
			}finally {
				if(byteArrayIn != null) {
					byteArrayIn.close();
				}
			}
		}
		if(!isPutObject) {
			throw new RuntimeException("putObject失败");
		}
		String url = bucketName + "/" + objectName;
		result.put("result", url);
		result.put("endpoint", minioBaseUrl);
		result.put("description", "success");
		result.put("error", "0");
	}
	/**
	 * 创建开放式bucket,不需要在页面客户端授权就可以访问资源
	 * @param bucketName
	 * @throws Exception
	 */
	public void createPublicBucket(String bucketName) throws Exception {
		if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
			client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
			StringBuilder builder = new StringBuilder();
			//字符串可以从页面客户端中提取
			builder.append("{\r\n" + 
					"    \"Version\": \"2012-10-17\",\r\n" + 
					"    \"Statement\": [\r\n" + 
					"        {\r\n" + 
					"            \"Effect\": \"Allow\",\r\n" + 
					"            \"Principal\": {\r\n" + 
					"                \"AWS\": [\r\n" + 
					"                    \"*\"\r\n" + 
					"                ]\r\n" + 
					"            },\r\n" + 
					"            \"Action\": [\r\n" + 
					"                \"s3:ListBucketMultipartUploads\",\r\n" + 
					"                \"s3:GetBucketLocation\",\r\n" + 
					"                \"s3:ListBucket\"\r\n" + 
					"            ],\r\n" + 
					"            \"Resource\": [\r\n" + 
					"                \"arn:aws:s3:::%s\"\r\n" + 
					"            ]\r\n" + 
					"        },\r\n" + 
					"        {\r\n" + 
					"            \"Effect\": \"Allow\",\r\n" + 
					"            \"Principal\": {\r\n" + 
					"                \"AWS\": [\r\n" + 
					"                    \"*\"\r\n" + 
					"                ]\r\n" + 
					"            },\r\n" + 
					"            \"Action\": [\r\n" + 
					"                \"s3:AbortMultipartUpload\",\r\n" + 
					"                \"s3:DeleteObject\",\r\n" + 
					"                \"s3:GetObject\",\r\n" + 
					"                \"s3:ListMultipartUploadParts\",\r\n" + 
					"                \"s3:PutObject\"\r\n" + 
					"            ],\r\n" + 
					"            \"Resource\": [\r\n" + 
					"                \"arn:aws:s3:::%s/*\"\r\n" + 
					"            ]\r\n" + 
					"        }\r\n" + 
					"    ]\r\n" + 
					"}");
			//格式化,替换%s
			String config = String.format(builder.toString(), bucketName, bucketName);
			client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(config).build());
		}
	}

}
